import { ComponentObjectPropsOptions, ExtractPropTypes, SetupContext, defineComponent, getCurrentInstance, provide, inject } from 'vue';
// ExtractPropTypes 作用：将定义好的类型解析出来，然后类型给变量使用
// const props = {
//     name: {type: String},
//     age: {type: Number, default: 10},
//     flag: {type: Boolean, required: true}
// }
// type PropsType = ExtractPropTypes<typeof props>
// const myProps:PropsType = {} as any

export function designComponent<
    PropsOptions extends Readonly<ComponentObjectPropsOptions>, // 用来推断
    Props extends Readonly<ExtractPropTypes<PropsOptions>>, // 用来解析
    Refer
>(options: {
    name?: string,
    props?: PropsOptions,
    provideRefer?: boolean,
    setup?: (props: Props, setupContext: SetupContext) => {
        refer?: Refer,
        render: () => any
    }
}) {
    const { setup, provideRefer, ...leftOptions} = options
    return {
        ...defineComponent({
            ...leftOptions,
            setup(props: Props, setupContext: SetupContext) {
                
                const ctx = getCurrentInstance() as any 

                if (!setup) { // 原生的setup
                    console.error('designComponent: setup is required')
                    return () => null
                }
                    
                const { refer, render } = setup(props, setupContext) // 原生的setup
                ctx._refer = refer // 可以拿到当前的上下文

                if(provideRefer) {
                    if(!leftOptions.name) {
                        console.error('designComponent: name is required when provideRefer is true!')
                    }else {
                        provide(`@@${leftOptions.name}`, refer)
                    }
                }
                return render
            }
        } as any),
        use: { // 返回值类型
            ref: (refName: string) => {
                const ctx = (getCurrentInstance() as any).ctx
                return {
                    get value() {  // mounted之前，ctx还没渲染完成，是undefined,所以用get 即计算属性
                        return (ctx as any).$refs[refName].$._refer as Refer|null // 代理对象
                    }
                }
            },
            inject: (defaultValue?: any) => {
                return inject(`@@${leftOptions.name}`, defaultValue) as Refer
            }
        }
    }
}
